/**
    Implementation of functionalities that regard the alternative command
*/
#include "channel.h"

#include "shm_common.h"
#include <sys/shm.h>

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

/* TODO (dematteis#1#): Questa cva modificata, anche tenendo conto del fatto che ci sono i validity vyte */
/*
   Alternative command descriptor.
   It contains a mutex for the mutual access, a condition variable for suspension/wakeup
   of the receiving process and the number of guards that compose it.
*/
struct alternative_t
{
	key_t key;  //key for shared memory segment access
	int id;

	pthread_mutex_t mutex;
	pthread_cond_t cond;

	int n;
};

/*
    Data structure for the representation of an alternative in the alternative command.
    It contains a reference to the channel, the priority and the boolean condition.

    All the alternatives of an alternative command will be allocated in the same shared memory
    segment of the alternative command descriptor.
*/
struct alt_channel_t
{
	struct channel_t *ch;
	int priority;
	bool cond;
};


// common functions
inline int __LClockAlt( struct alternative_t *alt ) {
	return pthread_mutex_lock( & alt->mutex );
}
inline int __LCunlockAlt( struct alternative_t *alt ) {
	return pthread_mutex_unlock( & alt->mutex );
}
inline int __LCwaitOnAlt( struct alternative_t *alt ) {
	return pthread_cond_wait( & alt->cond, & alt->mutex );
}
inline int __LCwakeUpAlt( struct alternative_t *alt ) {
	return pthread_cond_signal( & alt->cond );
}

/*
    This function is called by LCsend in case that a send is executed on a channel
    that has the altKey setted
    */
void __LCwakeUpAlternative( key_t altKey )
{
	struct alternative_t *alt;

    //acquire the alternative channel descriptor
	int id = shmget( altKey, sizeof(*alt), 0700 );
	if( id == -1 ) {
		fprintf( stderr, "[ALT] wakeup::shmget(): %s\n", strerror(errno) );
		return ;
	}

	alt = shmat( id, 0, 0 );
	if( ! alt ) {
		fprintf( stderr, "[ALT] wakeup::shmat(): %s\n", strerror(errno) );
		return ;
	}

	__LClockAlt( alt );
	__LCwakeUpAlt( alt ); //wakeup the partner that is polling
	__LCunlockAlt( alt );

    //detatch
	shmdt( alt );
}
// end common functions


// returns the address of the i-th alt_channel of alternative alt
#define ALTCH(alt,i) ( (struct alt_channel_t*) ( (char*) alt + sizeof(*alt) + sizeof(struct alt_channel_t)*i ) )

/*
    Creation of an alternative command with n guards
*/
struct alternative_t *LCcreate_alt( key_t key, int n )
{
	struct alternative_t *alt;

    //creation and attachment of the shared memory segment that will contain the alternative descriptor
    //and the guards
	int id = shmget( key, sizeof(*alt) + sizeof(struct alt_channel_t)*(n), IPC_CREAT | 0700 );
	if( id == -1 ) {
		fprintf( stderr, "receiver::shmget(): %s\n", strerror(errno) );
		return NULL;
	}

	alt = shmat( id, 0, 0 );
	if( alt == (void*) -1 ) {
		fprintf( stderr, "receiver::shmat(): %s\n", strerror(errno) );
		return NULL;
	}

	// setting alternative key and id
	alt->key = key;
	alt->id = id;

	// initing mutex and condition variable
	// (it's mandatory to set them as shared objects!)
	mutex_init( & alt->mutex );
	cond_init( & alt->cond );

	// setting other fields of the channel (initialize the guards at a default value)
	alt->n = n;
	int i;
	for( i = 0; i < n; i ++ ) {
		struct alt_channel_t *altch = ALTCH(alt,i);
		altch->cond = 0;
		altch->ch = 0;
		altch->priority = 0;
	}

	return alt;
}

/*
    Set of a guard into an alternative command
*/
void LCsetAlt( struct alternative_t *alt, int id, struct channel_t *ch, int priority, bool cond )
{
	struct alt_channel_t *altch = ALTCH(alt,id);
	altch->ch = ch;
	altch->priority = priority;
	altch->cond = cond;
}


/*
    Returns the best alternative channel id to read from
    -1 if no channel is ready yet
    -2 if no channel is ever going to be ready (all conds are set to false)
*/
static int __getBestChId( struct alternative_t *alt, key_t altKey )
{
	int bestId = -2;
	int max = INT_MIN;

	int i;
	for( i = 0; i < alt->n; i ++ ) { //for all the guards
		struct alt_channel_t *altch = ALTCH(alt,i);
		struct channel_t *ch = altch->ch;

		// not polling ch (the predicate is false, goes ahead...)
		if( ! altch->cond ) {
			continue;
		}

		// polling ch
		if( ch ) { //not a SKIP

			// locking it and setting altKey (the sender, will se the altKey setted
            // and will wake up the partner)

			__LClockChannel( ch );
			__LCsetAltKey( ch, altKey );

			// ch isn't ready yet (otherwise the partner has performed a send
            // before that the alternative command is invoked and the altKey setted)
			if( ! LCreadyToReceive(ch) ) {
				if( bestId == -2 ) {
					bestId = -1;
				}
				__LCunlockChannel( ch );
				continue;
			}

			__LCunlockChannel( ch );
		}

		// a comodity macro
		#define SETBEST { bestId=i; max=altch->priority; }

        //get the choiche with maximum priority

		if( bestId < 0 ) {
			// no best id yet: this is the best one so far
			SETBEST;
			continue;
		}
		if( altch->priority > max ) {
			// ch has highest priority so far
			SETBEST;
			continue;
		}

		#undef SETBEST
	}

	return bestId;
}


/*
    This function returns the id of the channel (between the ones that are part of the
    alternative command) that is eligible for a receive. Return -1 if no channel is ready.
    (further details on Giangrandi's thesis, pag 41-42)
*/
int LCpoll( struct alternative_t *alt, struct channel_t **ch )
{
	int altchId;
	int i;

	__LClockAlt( alt );

    //check if there are channel ready to receive.
	altchId = __getBestChId( alt, alt->key );
	while( altchId == -1 ) {
		// while no channel is ready... waiting
		__LCwaitOnAlt( alt );
		altchId = __getBestChId( alt, alt->key );
	}

    /* At this point we have that at least one guard is active.
        The altKey field of each channel is resetted and the id of the
        guard is returned to the caller
    */
	// resetting channels' alt key
	for( i = 0; i < alt->n; i ++ ) {
		struct channel_t *ch = ALTCH(alt, i)->ch;

		__LClockChannel( ch );
		__LCsetAltKey( ch, 0 );
		__LCunlockChannel( ch );
	}

	__LCunlockAlt( alt );

	// return values
	if( altchId == -2 ) {
		*ch = 0;
		return -1;
	}
	*ch = ALTCH(alt,altchId)->ch; //save the reference of the channel
	return altchId;
}
